//
//  SEIMManager.swift
//  SmartEyeForPatient
//
//  Created by QDSG on 2021/4/27.
//

import UIKit
import Starscream
import Alamofire

private let kHeartbeatTimeInterval: TimeInterval = 5    // 每隔 5 秒发送心跳包
private let kResendMsgTimerInterval: TimeInterval = 5   // 如果消息隔 5 秒还没收到发送成功时，重发消息

class SEIMManager: NSObject {
    private static var _shared: SEIMManager?
    
    class var shared: SEIMManager {
        struct Static {
            static let instance = SEIMManager()
        }
        
        guard let shared = _shared else {
            _shared = Static.instance
            return _shared!
        }
        
        return shared
    }
    
    /// 销毁单例
    class func destroy() {
        _shared?.disconnect()
        _shared = nil
        SELog("_shared: \(_shared)")
    }
    
    deinit {
        SELog("timer销毁")
        NotificationCenter.default.removeObserver(self)
    }
    
    private var isInChatVc: Bool {
        return UIViewController.current is SEChatViewController
    }
    
    var sendMessageHandler: ((SEChatRecord) -> Void)?
    var receiveMessageHandler: ((SEChatRecord) -> Void)?
    var pushMessageHandler: ((SEChatRecord) -> Void)?
    var logoutMessageHandler: (() -> Void)?
    
    private var socket: WebSocket?
    
    private lazy var reachabilityManager = NetworkReachabilityManager(host: "www.baidu.com")
    
    private var isConnected = false
    private var autoConnect = false
    
    private weak var heartbeatTimer: Timer?
    private var heartbeatCount: Int = 0     // 心跳次数 > 3 表示已经断连，提示重连
    
    private weak var resendMsgTimer: Timer?
    private var resendMsgCount: Int = 0     // 重发消息 > 3 表示消息发送失败
    
    private var waitSendMsgList = [SEChatRecord]()
    private let semaphore = DispatchSemaphore(value: 1)
    private let sendMessageQueue = DispatchQueue(label: "sendMessageQueue")
    private let sendMessageGroup = DispatchGroup()
    
    override init() {
        super.init()
        
        NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: UIApplication.willResignActiveNotification, object: nil)
        
        reachabilityManager?.startListening(onUpdatePerforming: { [weak self] (status) in
            SELog("网络状态：\(status)")
            guard let self = self else { return }
            switch status {
            case .reachable(.ethernetOrWiFi), .reachable(.cellular):
                guard !self.isConnected else { return }
                
                if self.autoConnect {
                    self.reconnect()
                }
            default:
                SEHUD.show(message: "请检查网络是否断开", position: .top)
                self.disconnect()
            }
        })
    }
}

// MARK: - notification event

private extension SEIMManager {
    @objc func didBecomeActive() {
        reconnect()
    }
    
    @objc func willResignActive() {
        disconnect()
    }
}

// MARK: - websocket

extension SEIMManager {
    func connect() {
        autoConnect = false
        initWebsocket()
    }
    
    private func reconnect() {
        if !isConnected && autoConnect {
            initWebsocket()
        }
    }
    
    func disconnect() {
        if socket != nil {
            socket?.forceDisconnect()
            socket?.delegate = nil
            socket = nil
        }
        autoConnect = true
        isConnected = false
        destoryHeartbeatTimer()
        destroyResendMsgTimer()
    }
    
    private func socketDidConnected() {
        autoConnect = true
        isConnected = true
        initHeartbeatTimer()
        
        if !waitSendMsgList.isEmpty {
            resendMessageQueue()
        }
    }
    
    private func initWebsocket() {
        SELog("websocket url = \(SEApiManager.websocketUrl)")
        let request = URLRequest(url: URL(string: SEApiManager.websocketUrl)!)
        let pinner = FoundationSecurity(allowSelfSigned: true) // 不验证 SSL 证书
        if socket == nil {
            socket = WebSocket(request: request, certPinner: pinner)
            socket?.delegate = self
            socket?.connect()
        }
    }
}

// MARK: - 定时器

private extension SEIMManager {
    // MARK: - 心跳定时器
    
    private func initHeartbeatTimer() {
        heartbeatCount = 0
        
        if heartbeatTimer == nil {
            heartbeatTimer = Timer.scheduledTimer(withTimeInterval: kHeartbeatTimeInterval, repeats: true) { _ in
                self.sendHeartbeat()
            }
        }
    }
    
    private func destoryHeartbeatTimer() {
        if heartbeatTimer != nil {
            heartbeatTimer?.invalidate()
            heartbeatTimer = nil
        }
    }
    
    // MARK: - 重发定时器
    
    private func initResendMsgTimer() {
        resendMsgCount = 0
        if resendMsgTimer == nil {
            resendMsgTimer = Timer.scheduledTimer(withTimeInterval: kResendMsgTimerInterval, repeats: true, block: { _ in
                self.resendMessage()
            })
            RunLoop.current.add(resendMsgTimer!, forMode: .common)
        }
    }
    
    private func destroyResendMsgTimer() {
        if resendMsgTimer != nil {
            resendMsgTimer?.invalidate()
            resendMsgTimer = nil
        }
    }
}

// MARK: - WebSocketDelegate

extension SEIMManager: WebSocketDelegate {
    func didReceive(event: WebSocketEvent, client: WebSocket) {
        switch event {
        case .connected(let header):
            SELog("websocket 已经连接: \(header)")
            socketDidConnected()
        case .disconnected(let reason, let code):
            isConnected = false
            SELog("websocket 已经断开连接: \(reason) - \(code)")
        case .cancelled:
            autoConnect = false
            isConnected = false
        case .error(let error):
            disconnect()
            handleError(error)
            reconnect()
        case .text(let string):
            handleReceiveMessage(string)
        default:
            break
        }
    }
    
    private func handleReceiveMessage(_ string: String) {
        guard var model = SEChatRecord.deserialize(from: string) else { return }
        let cmd = SEChatCMD(rawValue: model.cmd) ?? .unknown
        
        if cmd != .heartbeat {
            SELog("接收的消息string: \(string)")
        }
        
        switch cmd {
        case .message:
            if model.code != 200 {
                receiveMessageHandler?(model)
                pushMessageHandler?(model)
                replyMessage(model, cmd: .receive)
                if isInChatVc {
                    replyMessage(model, cmd: .haveRead)
                }
            }
        case .heartbeat:
            heartbeatCount = 0
        case .sendSuccess:
            resendMsgCount = 0
            handleSendMessage(.success)
            semaphore.signal()
            sendMessageGroup.leave()
        case .inquiryStatusChanged:
            let extras = SEChatRecordExtras.deserialize(from: model.extras)
            SEHUD.show(message: extras?.status.hudMessage, position: .top)
            model.status = extras?.status ?? .waitPay
            receiveMessageHandler?(model)
            pushMessageHandler?(model)
            replyMessage(model, cmd: .receive, ackType: .push)
        case .guideInquiryEnd:
            SEHUD.show(message: model.content, position: .top)
            receiveMessageHandler?(model)
            replyMessage(model, cmd: .receive, ackType: .push)
        case .prescriptionStatusChanged:
            let extras = SEChatRecordExtras.deserialize(from: model.extras)
            if extras?.prescriptionDto?.status == .passed {
                SEHUD.show(message: "处方已经审核了")
                model.prescription = extras?.prescriptionDto
                model.msgType = SEChatMessageType.prescription.rawValue
                receiveMessageHandler?(model)
            } else if extras?.prescriptionDto?.status == .rejected {
                SEHUD.show(message: "处方被驳回了")
            }
            replyMessage(model, cmd: .receive, ackType: .push)
            if isInChatVc {
                replyMessage(model, cmd: .haveRead)
            }
        case .logout:
            disconnect()
            logoutMessageHandler?()
        default:
            break
        }
    }
}

extension SEIMManager {
    func sendMessage(_ chatRecord: SEChatRecord) {
        let cmd = SEChatCMD(rawValue: chatRecord.cmd) ?? .unknown
        
        if cmd != .heartbeat {
            SELog("发送的消息 = \(chatRecord)")
        }
        
        switch cmd {
        case .message:
            waitSendMsgList.append(chatRecord)
            resendMessageQueue()
        default:
            guard let jsonStr = chatRecord.toJSONString() else { return }
            socket?.write(string: jsonStr)
        }
    }
    
    func resendMessageQueue() {
        initResendMsgTimer()
        SELog("待发送的消息数量: \(waitSendMsgList.count)")
        
        waitSendMsgList.forEach { model in
            guard let jsonStr = model.toJSONString() else {
                handleSendMessage(.failure)
                return
            }
            
            sendMessageGroup.enter()
            sendMessageQueue.async { [weak self] in
                guard let self = self else { return }
                SELog("发消息msgId ==== \(model.msgId ?? "====")")
                self.semaphore.wait() // 让信号量-1，比如默认值时0，wait()以后就变成了－1，因此会等待
                self.socket?.write(string: jsonStr)
            }
        }
        sendMessageGroup.notify(queue: DispatchQueue.main) {
            SELog("发送完成")
            SELog("待发送的消息数量: \(self.waitSendMsgList.count)")
            self.destroyResendMsgTimer()
        }
    }
    
    private func replyMessage(_ receive: SEChatRecord, cmd: SEChatCMD, ackType: SEChatMessageFromType = .private) {
        let model = SEChatRecord(fromUserId: receive.toUserId,
                                 toUserId: receive.fromUserId,
                                 relationId: receive.relationId,
                                 msgId: receive.msgId,
                                 ackType: ackType.rawValue,
                                 cmd: cmd.rawValue)
        sendMessage(model)
    }
    
    private func sendHeartbeat() {
        SELog("heartbeatCount ====== \(heartbeatCount)")
        
        guard heartbeatCount <= 3 else {
            SEHUD.show(message: "可能断开连接，正在尝试重连...", position: .top)
            disconnect()
            reconnect()
            return
        }
        
        let model = SEChatRecord(cmd: SEChatCMD.heartbeat.rawValue, hbbyte: "hb")
        sendMessage(model)
        
        heartbeatCount += 1
    }
    
    private func resendMessage() {
        SELog("resendMsgCount ====== \(resendMsgCount)")
        
        guard resendMsgCount <= 3 else {
            resendMsgCount = 0
            handleSendMessage(.failure)
            semaphore.signal()
            sendMessageGroup.leave()
            return
        }
        
        guard let model = waitSendMsgList.first, let jsonStr = model.toJSONString() else {
            handleSendMessage(.failure)
            semaphore.signal()
            sendMessageGroup.leave()
            return
        }
        socket?.write(string: jsonStr)
        SELog("重发消息msgId ==== \(model.msgId ?? "++++")")
        
        resendMsgCount += 1
    }
    
    private func handleSendMessage(_ status: SEChatMessageSendStatus) {
        if !waitSendMsgList.isEmpty {
            var model = waitSendMsgList.removeFirst()
            model.messageSendStatus = status
            sendMessageHandler?(model)
        }
    }
}

// MARK: - Helpers

private extension SEIMManager {
    func handleError(_ error: Error?) {
        if let e = error as? WSError {
            print("websocket encountered an error: \(e.message)")
        } else if let e = error {
            print("websocket encountered an error: \(e.localizedDescription)")
        } else {
            print("websocket encountered an error")
        }
    }
}
